package org.jeecg.access.platform.util;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;

import javax.annotation.PostConstruct;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * HTTP/HTTPS请求工具类
 * 支持业务代码通过回调接口自定义重试判断逻辑
 * 内部实现了请求重试、超时控制、线程池管理等功能
 */
@Component
@Slf4j
public class HttpRequestUtil {

    // 线程池核心参数配置
    private static final int CORE_POOL_SIZE = 10;        // 核心线程数
    private static final int MAX_POOL_SIZE = 50;         // 最大线程数
    private static final int QUEUE_CAPACITY = 1000;      // 任务队列容量
    private static final long KEEP_ALIVE_TIME = 60L;     // 空闲线程存活时间(秒)

    // HTTP请求默认配置常量
    private static final int DEFAULT_CONNECT_TIMEOUT = 2000;  // 默认连接超时时间(毫秒)
    private static final int DEFAULT_READ_TIMEOUT = 3000;     // 默认读取超时时间(毫秒)
    private static final int DEFAULT_RETRY_TIMES = 0;         // 默认重试次数
    private static final long DEFAULT_FIXED_INTERVAL = 1000L; // 默认重试间隔(毫秒)

    // 请求处理线程池
    private ExecutorService requestExecutor;

    // 配置管理器，用于加载和匹配请求配置
    @Autowired
    private RequestConfigManager configManager;

    /**
     * 重试决策接口
     * 由业务代码实现，用于判断当前响应是否需要触发重试
     */
    public interface RetryDecision {
        /**
         * 判断当前响应是否需要触发重试
         * @param responseCode HTTP响应码
         * @param response 响应内容字符串
         * @return true-需要重试（业务处理失败），false-不需要重试（业务处理成功）
         */
        boolean needRetry(int responseCode, String response);
    }

    /**
     * 初始化方法，在Bean初始化时创建线程池
     * 使用@PostConstruct注解确保在依赖注入完成后执行
     */
    @PostConstruct
    public void init() {
        // 创建自定义线程池
        requestExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                // 使用有界队列避免内存溢出
                new LinkedBlockingQueue<>(QUEUE_CAPACITY),
                // 自定义线程工厂，设置线程名称
                new ThreadFactory() {
                    private final AtomicInteger counter = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "http-request-thread-" + counter.getAndIncrement());
                    }
                },
                // 当队列满时，让提交任务的线程执行任务，避免任务丢失
                new ThreadPoolExecutor.CallerRunsPolicy());

        log.info("HTTP请求工具类初始化完成，线程池大小: {}-{}", CORE_POOL_SIZE, MAX_POOL_SIZE);
    }

    /**
     * 发送HTTP请求的核心方法
     * 支持同步阻塞方式获取结果，内部通过线程池处理请求
     *
     * @param url 请求的URL地址
     * @param method HTTP请求方法 (GET/POST/PUT/DELETE等)
     * @param headers 请求头信息Map
     * @param body 请求体内容字符串
     * @param retryDecision 重试决策器，由业务代码提供
     * @return 最终响应内容字符串，失败时返回null
     */
    public String sendRequest(String url, String method, Map<String, String> headers, String body, RetryDecision retryDecision) {
        // 创建请求任务
        RequestTask task = new RequestTask(url, method, headers, body, retryDecision);
        // 提交任务到线程池并获取Future对象
        Future<String> future = requestExecutor.submit(task);

        try {
            // 阻塞等待任务执行完成并返回结果
            return future.get();
        } catch (Exception e) {
            log.error("请求执行异常, URL: {}", url, e);
            return null; // 执行异常时返回null，由业务层处理
        }
    }

    /**
     * 请求任务内部类
     * 实现Callable接口，封装单次请求的完整逻辑，包括重试机制
     */
    private class RequestTask implements Callable<String> {
        private final String url;                // 请求URL
        private final String method;             // 请求方法
        private final Map<String, String> headers; // 请求头
        private final String body;               // 请求体
        private final RetryDecision retryDecision; // 重试决策器
        private int retryCount = 0;              // 当前重试次数

        /**
         * 构造方法初始化请求参数
         */
        public RequestTask(String url, String method, Map<String, String> headers, String body, RetryDecision retryDecision) {
            this.url = url;
            this.method = method.toUpperCase(); // 统一转为大写
            // 避免空指针，若headers为null则创建空Map
            this.headers = headers != null ? headers : new HashMap<>();
            this.body = body;
            this.retryDecision = retryDecision;
        }

        /**
         * 执行请求的核心方法
         * 包含完整的请求-重试逻辑
         */
        @Override
        public String call() {
            // 根据URL获取匹配的配置
            RequestConfig config = configManager.getMatchedConfig(url);
            // 确定最大重试次数，配置中没有则使用默认值
            int maxRetries = config.getRetryTimes() != null ? config.getRetryTimes() : DEFAULT_RETRY_TIMES;

            long startTime = 0;                  // 请求开始时间
            long endTime = 0;                    // 请求结束时间
            String finalResponse = null;         // 最终返回的响应内容
            int finalResponseCode = -1;          // 最终响应码

            // 循环执行请求，包含初始请求和最多maxRetries次重试
            while (retryCount <= maxRetries) {
                startTime = System.currentTimeMillis();
                HttpURLConnection connection = null; // HTTP连接对象
                String response = null;              // 本次请求的响应内容
                int responseCode = -1;               // 本次请求的响应码
                Exception exception = null;          // 可能发生的异常
                // 标记失败类型
                boolean isNetworkOrHttpFail = false; // 网络或HTTP状态失败
                boolean isBusinessFail = false;      // 业务逻辑失败
                boolean needRetry = false;           // 是否需要重试

                try {
                    // 创建URL对象
                    URL requestUrl = new URL(url);
                    // 打开连接
                    connection = (HttpURLConnection) requestUrl.openConnection();

                    // 设置超时时间，优先使用配置中的值，否则使用默认值
                    int connectTimeout = config.getConnectTimeout() != null ? config.getConnectTimeout() : DEFAULT_CONNECT_TIMEOUT;
                    int readTimeout = config.getReadTimeout() != null ? config.getReadTimeout() : DEFAULT_READ_TIMEOUT;
                    connection.setConnectTimeout(connectTimeout);
                    connection.setReadTimeout(readTimeout);

                    // 设置请求方法
                    connection.setRequestMethod(method);

                    // 设置请求头
                    for (Map.Entry<String, String> entry : headers.entrySet()) {
                        connection.setRequestProperty(entry.getKey(), entry.getValue());
                    }

                    // 处理有请求体的方法(POST/PUT/PATCH)
                    if (body != null && !body.isEmpty() &&
                            ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method))) {
                        connection.setDoOutput(true); // 允许输出
                        // 通过输出流写入请求体
                        try (OutputStream os = connection.getOutputStream()) {
                            byte[] input = body.getBytes("utf-8"); // 使用UTF-8编码
                            os.write(input, 0, input.length);
                        }
                    }

                    // 获取响应码
                    responseCode = connection.getResponseCode();

                    // 根据响应码选择输入流(正常响应或错误响应)
                    InputStream inputStream = responseCode >= 400 ? connection.getErrorStream() : connection.getInputStream();
                    // 读取响应内容
                    try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"))) {
                        StringBuilder responseBuilder = new StringBuilder();
                        String line;
                        // 逐行读取响应内容
                        while ((line = br.readLine()) != null) {
                            responseBuilder.append(line);
                        }
                        response = responseBuilder.toString();
                    }

                    // 记录当前响应作为可能的最终响应
                    finalResponse = response;
                    finalResponseCode = responseCode;

                    // 判断是否需要重试
                    if (responseCode < 200 || responseCode >= 300) {
                        // HTTP状态码不在200-299范围内，属于网络/HTTP层失败
                        isNetworkOrHttpFail = true;
                        needRetry = true;
                    } else {
                        // HTTP状态码正常，由业务决策器判断是否需要重试
                        if (retryDecision != null && retryDecision.needRetry(responseCode, response)) {
                            isBusinessFail = true;
                            needRetry = true;
                        } else {
                            // 业务处理成功，无需重试，直接返回响应
                            log.info("请求成功（无重试）, URL: {}, 响应码: {}", url, responseCode);
                            return response;
                        }
                    }

                } catch (Exception e) {
                    // 捕获所有异常(连接超时、读取超时等)，标记为网络失败
                    exception = e;
                    isNetworkOrHttpFail = true;
                    needRetry = true;
                } finally {
                    // 确保连接被关闭
                    if (connection != null) {
                        connection.disconnect();
                    }
                }

                // 记录失败信息
                endTime = System.currentTimeMillis();
                logFailedRequest(startTime, endTime, responseCode, response, exception, isNetworkOrHttpFail, isBusinessFail);

                // 判断是否终止重试循环
                if (!needRetry || retryCount >= maxRetries) {
                    break;
                }

                // 准备进行下一次重试
                retryCount++;
                log.warn("准备第{}次重试, URL: {}", retryCount, url);
                try {
                    // 等待重试间隔
                    waitInterval(config);
                } catch (InterruptedException e) {
                    log.error("重试等待被中断, URL: {}", url, e);
                    Thread.currentThread().interrupt(); // 保留中断状态
                    break;
                }
            }

            // 达到最大重试次数，返回最后一次的响应
            if (retryCount >= maxRetries) {
                log.warn("达到最大重试次数({}), 终止重试, URL: {}", maxRetries, url);
                return finalResponse;
            }

            // 其他情况返回最终响应
            return finalResponse;
        }

        /**
         * 根据配置等待重试间隔
         * 支持固定间隔和随机间隔两种模式
         *
         * @param config 请求配置对象
         * @throws InterruptedException 线程中断异常
         */
        private void waitInterval(RequestConfig config) throws InterruptedException {
            long interval = DEFAULT_FIXED_INTERVAL;
            // 如果配置了间隔时间则使用配置的值
            if (config.getInterval() != null) {
                interval = config.getInterval();
                // 如果启用了随机间隔，且配置了最小和最大间隔
                if (Boolean.TRUE.equals(config.getRandomInterval()) && config.getMinInterval() != null && config.getMaxInterval() != null) {
                    long min = config.getMinInterval();
                    long max = config.getMaxInterval();
                    // 生成[min, max)之间的随机间隔
                    interval = min + (long) (Math.random() * (max - min));
                }
            }
            log.debug("重试间隔: {}ms, URL: {}", interval, url);
            Thread.sleep(interval);
        }

        /**
         * 记录请求失败的详细信息
         * 包括请求头、请求报文、响应内容等关键信息，便于问题排查
         *
         * @param startTime 请求开始时间
         * @param endTime 请求结束时间
         * @param responseCode 响应码
         * @param response 响应内容
         * @param isNetworkOrHttpFail 是否为网络/HTTP失败
         * @param isBusinessFail 是否为业务失败
         */
        private void logFailedRequest(long startTime, long endTime, int responseCode,
                                      String response, Exception e, boolean isNetworkOrHttpFail, boolean isBusinessFail) {
            long duration = endTime - startTime;
            double seconds = duration / 1000.0;

            StringBuilder logMsg = new StringBuilder();
            logMsg.append("\n=== 请求失败详情 ===");
            // 记录失败类型
            logMsg.append("\n类型: ");
            if (isNetworkOrHttpFail) {
                logMsg.append("网络/HTTP状态失败");
            } else if (isBusinessFail) {
                logMsg.append("业务失败");
            }
            // 记录基本请求信息
            logMsg.append("\nURL: ").append(url);
            logMsg.append("\n方法: ").append(method);
            logMsg.append("\n响应码: ").append(responseCode);
            logMsg.append("\n耗时: ").append(String.format("%.3f", seconds)).append("秒 (").append(duration).append("ms)");

            // 记录请求头信息，对敏感信息进行脱敏
            logMsg.append("\n请求头: {");
            if (headers != null && !headers.isEmpty()) {
                List<String> headerEntries = new ArrayList<>();
                // 遍历所有请求头
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    // 对认证信息和Cookie进行脱敏处理
//                    if ("Authorization".equalsIgnoreCase(key) || "Cookie".equalsIgnoreCase(key)) {
//                        headerEntries.add(key + ": ******");
//                    } else {
                        headerEntries.add(key + ": " + value);
//                    }
                }
                // 拼接所有请求头
                logMsg.append(String.join(", ", headerEntries));
            }
            logMsg.append("}");

            // 记录请求报文
            if (body != null && !body.isEmpty()) {
                logMsg.append("\n请求报文: ").append(body);
            }

            // 记录响应内容
            if (response != null) {
                logMsg.append("\n响应体: ").append(response);
            }
            // 记录异常信息
            if (e != null) {
                logMsg.append("\n异常: ").append(e.getMessage());
            }
            logMsg.append("\n=================");

            // 输出错误日志
            log.error(logMsg.toString());
        }
    }
}

/**
 * 请求配置管理类
 * 负责从配置文件加载请求配置，并根据URL匹配最合适的配置
 */
@Component
@Slf4j
class RequestConfigManager {

    // 存储所有加载的请求配置
    private List<RequestConfig> configs = new ArrayList<>();

    /**
     * 根据URL获取匹配的配置
     * 匹配规则：URL前缀匹配，最长前缀优先
     *
     * @param url 请求URL
     * @return 合并后的请求配置
     */
    public RequestConfig getMatchedConfig(String url) {
        List<RequestConfig> matchedConfigs = new ArrayList<>();
        // 查找所有URL前缀匹配的配置
        for (RequestConfig config : configs) {
            if (url.startsWith(config.getUrl())) {
                matchedConfigs.add(config);
            }
        }

        // 按URL长度降序排序，确保最长前缀优先
        matchedConfigs.sort(new Comparator<RequestConfig>() {
            @Override
            public int compare(RequestConfig c1, RequestConfig c2) {
                return Integer.compare(c2.getUrl().length(), c1.getUrl().length());
            }
        });

        // 从默认配置开始，合并所有匹配的配置
        RequestConfig mergedConfig = new RequestConfig();

//                getDefaultConfig();
        for (RequestConfig config : matchedConfigs) {
            if (config.getConnectTimeout() != null) {
                mergedConfig.setConnectTimeout(config.getConnectTimeout());
            }
            if (config.getReadTimeout() != null) {
                mergedConfig.setReadTimeout(config.getReadTimeout());
            }
            if (config.getInterval() != null) {
                mergedConfig.setInterval(config.getInterval());
            }
            if (config.getRandomInterval() != null) {
                mergedConfig.setRandomInterval(config.getRandomInterval());
            }
            if (config.getMinInterval() != null) {
                mergedConfig.setMinInterval(config.getMinInterval());
            }
            if (config.getMaxInterval() != null) {
                mergedConfig.setMaxInterval(config.getMaxInterval());
            }
            if (config.getRetryTimes() != null) {
                mergedConfig.setRetryTimes(config.getRetryTimes());
            }
            if (config.getFailureCondition() != null) {
                mergedConfig.setFailureCondition(config.getFailureCondition());
            }
        }

        log.debug("URL: {} 匹配的合并配置: {}", url, mergedConfig);
        return mergedConfig;
    }

    /**
     * 获取默认请求配置
     *
     * @return 默认配置对象
     */
//    private RequestConfig getDefaultConfig() {
//        RequestConfig defaultConfig = new RequestConfig();
//        defaultConfig.setConnectTimeout(2000);
//        defaultConfig.setReadTimeout(3000);
//        defaultConfig.setInterval(1000L);
//        defaultConfig.setRandomInterval(false);
//        defaultConfig.setRetryTimes(0);
//        return defaultConfig;
//    }

    /**
     * 从配置文件加载请求配置
     * 在Bean初始化时执行
     */
    @PostConstruct
    public void loadConfigs() {
        try {
            Yaml yaml = new Yaml();
            // 从类路径加载配置文件
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("http-config.yml");
            if (inputStream != null) {
                Map<String, Object> yamlMap = yaml.load(inputStream);
                Map<String, Object> httpConfig = (Map<String, Object>) yamlMap.get("http");
                if (httpConfig != null) {
                    List<Map<String, Object>> configList = (List<Map<String, Object>>) httpConfig.get("config");
                    if (configList != null) {
                        // 遍历配置列表，转换为RequestConfig对象
                        for (Map<String, Object> configMap : configList) {
                            RequestConfig config = new RequestConfig();
                            config.setUrl((String) configMap.get("url"));
                            config.setConnectTimeout(toInteger(configMap.get("connect-timeout")));
                            config.setReadTimeout(toInteger(configMap.get("read-timeout")));
                            config.setInterval(toLong(configMap.get("interval")));
                            config.setRandomInterval((Boolean) configMap.get("random-interval"));
                            config.setMinInterval(toLong(configMap.get("min-interval")));
                            config.setMaxInterval(toLong(configMap.get("max-interval")));
                            config.setRetryTimes(toInteger(configMap.get("retry-times")));
                            config.setFailureCondition((String) configMap.get("failure-condition"));
                            configs.add(config);
                        }
                        log.info("从配置文件加载{}条请求配置", configList.size());
                        return;
                    }
                }
            }
        } catch (Exception e) {
            log.error("加载HTTP配置文件失败", e);
        }
    }

    /**
     * 将对象转换为Integer类型
     * 支持Integer和String类型的输入
     *
     * @param value 待转换的值
     * @return 转换后的Integer，为null则返回null
     */
    private Integer toInteger(Object value) {
        if (value == null) return null;
        if (value instanceof Integer) {
            return (Integer) value;
        }
        return Integer.valueOf(value.toString());
    }

    /**
     * 将对象转换为Long类型
     * 支持Long、Integer和String类型的输入
     *
     * @param value 待转换的值
     * @return 转换后的Long，为null则返回null
     */
    private Long toLong(Object value) {
        if (value == null) return null;
        if (value instanceof Long) {
            return (Long) value;
        }
        if (value instanceof Integer) {
            return ((Integer) value).longValue();
        }
        return Long.valueOf(value.toString());
    }
}

/**
 * 请求配置类
 * 存储单个URL模式的请求配置参数
 * 使用lombok的@Data注解自动生成getter、setter等方法
 */
@Data
class RequestConfig {
    private String url;                // URL前缀，用于匹配请求
    private Integer connectTimeout;    // 连接超时时间(毫秒)
    private Integer readTimeout;       // 读取超时时间(毫秒)
    private Long interval;             // 重试间隔(毫秒)
    private Boolean randomInterval;    // 是否启用随机重试间隔
    private Long minInterval;          // 最小重试间隔(毫秒)，用于随机模式
    private Long maxInterval;          // 最大重试间隔(毫秒)，用于随机模式
    private Integer retryTimes;        // 重试次数
    private String failureCondition;   // 失败条件表达式
}
